package com.chia.multienty.workflow.service.impl;

import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSONObject;
import com.chia.multienty.core.domain.bo.workflow.ProcessDeployResult;
import com.chia.multienty.core.domain.bo.workflow.ProcessStartResult;
import com.chia.multienty.core.domain.enums.ApplicationType;
import com.chia.multienty.core.domain.enums.FlowableVariables;
import com.chia.multienty.core.domain.enums.HttpResultEnum;
import com.chia.multienty.core.domain.enums.StatusEnum;
import com.chia.multienty.core.exception.KutaRuntimeException;
import com.chia.multienty.core.parameter.workflow.*;
import com.chia.multienty.core.pojo.WorkflowProcess;
import com.chia.multienty.core.service.WorkflowProcessService;
import com.chia.multienty.core.util.ListUtil;
import com.chia.multienty.workflow.parameter.FlowableProcessInstanceDeleteParameter;
import com.chia.multienty.workflow.parameter.FlowableProcessInstanceListGetParameter;
import com.chia.multienty.workflow.service.FlowableProcessService;
import io.jsonwebtoken.lang.Strings;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class FlowableProcessServiceImpl implements FlowableProcessService {

    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final ProcessEngine processEngine;
    private final IdentityService identityService;
    private final WorkflowProcessService workflowProcessService;
    private final ProcessEngineConfiguration processEngineConfiguration;

    @Override
    public ProcessDeployResult deploy(FlowableDeployParameter parameter) {
        String xml = FileUtil.readString(parameter.getFilePath(), "utf-8");
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deploy = repositoryService.createDeployment()
                .name(parameter.getName())
                .category(parameter.getCategory())
                .key(parameter.getKey())
                .tenantId(parameter.getTenantId())
                .addString(parameter.getResourceName(), xml)
                .deploy();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("已部署流程:{}", processDefinition.getKey());
        return new ProcessDeployResult()
                .setDeployId(deploy.getId())
                .setDeployKey(deploy.getKey())
                .setProcessDefinitionId(processDefinition.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessDeployResult redeploy(FlowableRedeployParameter parameter) {
        RepositoryService repositoryService = processEngine.getRepositoryService();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(parameter.getDeployedId())
                .singleResult();
        if(processDefinition != null) {
            repositoryService.suspendProcessDefinitionById(processDefinition.getId(), true, null);
        }

        List<String> processInsList = getProcessInsList(new FlowableProcessInstanceListGetParameter()
                .setDeploymentId(parameter.getDeployedId())
                .setProcessDefinitionId(processDefinition.getId())
        );
        if(ListUtil.isNotEmpty(processInsList)) {
            for (String processIns : processInsList) {
                deleteProcessIns(new FlowableProcessInstanceDeleteParameter()
                        .setProcessInstanceId(processIns).setReason("重新部署"));
            }
        }

        Deployment deployment = repositoryService.createDeploymentQuery()
                .deploymentId(parameter.getDeployedId()).singleResult();
        //发布新流程
        ProcessDeployResult result = deploy(new FlowableDeployParameter()
                .setCategory(deployment.getCategory())
                .setFilePath(parameter.getFilePath())
                .setKey(deployment.getKey())
                .setName(deployment.getName())
                .setResourceName(processDefinition.getResourceName())
                .setTenantId(deployment.getTenantId())
        );
        //删除旧流程
        repositoryService.deleteDeployment(parameter.getDeployedId());

        return result;
    }

    @Override
    public void deleteDeploy(FlowableDeployDeleteParameter parameter) {
        RepositoryService repositoryService = processEngine.getRepositoryService();
        repositoryService.deleteDeployment(parameter.getDeployedId());
    }

    @Override
    public ProcessStartResult start(FlowableProcessStartParameter parameter) {
        identityService.setAuthenticatedUserId(parameter.getAuthenticatedUserId());
        ProcessInstance processInstance = null;
        try {
            if(parameter.getTenantId() == null) {
                processInstance = runtimeService.startProcessInstanceByKey(
                        parameter.getKey(), parameter.getName(), parameter.getVariables());
            }
            else {
                processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(
                        parameter.getKey(),
                        parameter.getName(),
                        parameter.getVariables(),
                        parameter.getTenantId());
            }

            return new ProcessStartResult()
                    .setProcessInstanceId(processInstance.getProcessInstanceId())
                    .setProcessDefinitionId(processInstance.getProcessDefinitionId());
        } catch (Exception ex) {
            log.error("流程启动失败,参数:{}", JSONObject.toJSONString(parameter));
            log.error("失败原因", ex);
            throw ex;
        }
    }

    @Override
    public List<String> getProcessInsList(FlowableProcessInstanceListGetParameter parameter) {
        List<ProcessInstance> list = runtimeService
                .createProcessInstanceQuery()
                .deploymentId(parameter.getDeploymentId())
                .list();

        return list.stream().map(m -> m.getId()).collect(Collectors.toList());
    }

    @Override
    public void deleteProcessIns(FlowableProcessInstanceDeleteParameter parameter) {

        runtimeService.deleteProcessInstance(parameter.getProcessInstanceId(), parameter.getReason());
    }

    @Override
    public List<Map<String, Object>> list(FlowableTaskListGetParameter parameter) {
        TaskQuery taskQuery = taskService.createTaskQuery();
        if(ListUtil.isNotEmpty(parameter.getGroupIds()) && parameter.getUserId() != null) {
            taskQuery.or().taskCandidateGroupIn(parameter.getGroupIds()).taskCandidateOrAssigned(parameter.getUserId()).endOr();
        }
        else if(ListUtil.isNotEmpty(parameter.getGroupIds()) && parameter.getUserId() == null) {
            taskQuery.taskCandidateGroupIn(parameter.getGroupIds());
        }
        else if(ListUtil.isEmpty(parameter.getGroupIds()) && parameter.getUserId() != null) {
            taskQuery.taskCandidateOrAssigned(parameter.getUserId());
        }
        if(parameter.getTenantId() != null) {
            taskQuery.processVariableValueGreaterThanOrEqual(FlowableVariables.TENANT_ID.name(), parameter.getTenantId());
        }
        List<Task> tasks = taskQuery.excludeSubtasks().orderByTaskCreateTime().asc().listPage(parameter.getOffset(), parameter.getLimit());
        List<Map<String, Object>> list = new ArrayList<>();
        for (int i = 0; i < tasks.size(); i++) {
            Task task = tasks.get(i);
            Map<String, Object> variables = taskService.getVariables(task.getId());
            variables.put(FlowableVariables.TASK_ID.name(), task.getId());
            variables.put(FlowableVariables.CATEGORY.name(), task.getCategory());
            variables.put(FlowableVariables.CREATE_TIME.name(), task.getCreateTime());
            variables.put(FlowableVariables.DESCRIBE.name(), task.getDescription());
            variables.put(FlowableVariables.TASK_NAME.name(), task.getName());
            variables.put(FlowableVariables.PROCESS_DEFINITION_ID.name(), task.getProcessDefinitionId());
            variables.put(FlowableVariables.PROCESS_INSTANCE_ID.name(), task.getProcessInstanceId());

            list.add(variables);
        }
        return list;
    }


    @Override
    public void claim(FlowableTaskClaimParameter parameter) {
        Task task =  taskService.createTaskQuery().taskId(parameter.getTaskId()).singleResult();
        if(task == null) {
            throw new KutaRuntimeException(HttpResultEnum.FLOWABLE_TASK_NOT_EXISTS);
        }
        taskService.claim(parameter.getTaskId(), parameter.getUserId());
    }

    @Override
    public void examine(FlowableExamineParameter parameter) {
        Task task =  taskService.createTaskQuery().taskId(parameter.getTaskId()).singleResult();
        if(task == null) {
            throw new KutaRuntimeException(HttpResultEnum.FLOWABLE_TASK_NOT_EXISTS);
        }
        if(Strings.hasText(parameter.getComment())) {
            taskService.addComment(parameter.getTaskId(), null, null, parameter.getComment());
        }
        parameter.getVariables().put(FlowableVariables.FEEDBACK.name(), parameter.getComment());
        taskService.complete(task.getId(), parameter.getVariables());
    }

    @Override
    public void revoke(FlowableProcessRevokeParameter parameter) {
        ProcessInstance process = runtimeService.createProcessInstanceQuery()
                .processDefinitionId(parameter.getProcessInstanceId()).singleResult();
        if(process == null) {
            throw new KutaRuntimeException(HttpResultEnum.FLOWABLE_PROCESS_NOT_EXISTS);
        }
        Task task = taskService.createTaskQuery()
                .processInstanceId(parameter.getProcessInstanceId())
                .singleResult();
        runtimeService.setVariable(task.getExecutionId(), "status", StatusEnum.CANCELLED.getCode());
        runtimeService.deleteProcessInstance(parameter.getProcessInstanceId(), parameter.getReason());
    }

}
